package it.uniroma2.svd.writer;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import it.uniroma2.svd.writer.BinaryMatrix;
import it.uniroma2.svd.writer.DenseBinaryMatrix;
import it.uniroma2.util.math.ArrayMath;
import it.uniroma2.util.tree.Tree;




public class DenseBinaryMatrix<T> implements BinaryMatrix<T> {
	private RandomAccessFile _matrixFile;
	private int n_row = -1;
	private int n_col = -1;
	private boolean sized = false;
	
	private int sizeType;
	private Class<T> tClass;
	private Method readMethod;
	private Method writeMethod;
	private int currentrow;
	public enum Operator {SUM, DIV, MULT}
	
	public DenseBinaryMatrix(Class<T> c){
		tClass = c;
		setType();
	}

	public void openFile(String file, String mode) throws IOException{ //mode "r" o "rw"
		File f = new File(file);
		boolean exist = f.exists();
		_matrixFile = new RandomAccessFile(file,mode);
		if(sized){
			long len = 8 + ((long)n_row * (long)n_col * (long)sizeType);
			_matrixFile.setLength(len);
			writeSize();
		}
		if(exist){
			readSize();
		}
	}
	public void openFile(String file, String mode, boolean sized) throws IOException{ //mode "r" o "rw"
		File f = new File(file);
		boolean exist = f.exists();
		_matrixFile = new RandomAccessFile(file,mode);
		if(sized){
			long len = 8 + ((long)n_row * (long)n_col * (long)sizeType);
			_matrixFile.setLength(len);
			writeSize();
		}
		if(exist){
			readSize();
		}
	}
	public void closeFile() throws IOException{
		_matrixFile.close();
	}
	
	public void writeInt(Integer i) throws IOException{_matrixFile.writeInt(i);}
	public void writeFloat(Float i) throws IOException{_matrixFile.writeFloat(i);}
	public void writeDouble(Double i) throws IOException{_matrixFile.writeDouble(i);}
	
	public Integer readInt() throws IOException{return _matrixFile.readInt();}
	public Float readFloat() throws IOException{return _matrixFile.readFloat();}
	public Double readDouble() throws IOException{return _matrixFile.readDouble();}
	public void setSize(int row, int col){
		n_col = col;
		n_row = row;
		sized = true;
	}
	public void setCol(int col){
		n_col = col;
	}
	public void setType(){
		try {
			if(tClass.equals(Integer.class)){
				sizeType = 4;
				readMethod = this.getClass().getMethod("readInt");
				writeMethod = this.getClass().getMethod("writeInt", new Class[]{Integer.class});
			}else if(tClass.equals(Float.class)){
				readMethod = this.getClass().getMethod("readFloat");
				writeMethod = this.getClass().getMethod("writeFloat", new Class[]{Float.class});
				sizeType = 4;
			}else if(tClass.equals(Double.class)){
				readMethod = this.getClass().getMethod("readDouble");
				writeMethod = this.getClass().getMethod("writeDouble", new Class[]{Double.class});
				sizeType = 8;
			} 
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
		
	}
	
	public void readSize() throws IOException{
		_matrixFile.seek(0);
		n_row = _matrixFile.readInt();
		n_col = _matrixFile.readInt();
		
	}
	
	public void writeSize() throws IOException{
		_matrixFile.seek(0);
		_matrixFile.writeInt(n_row);
		_matrixFile.writeInt(n_col);
		_matrixFile.setLength(getPos(n_row, n_col)+sizeType);
	}
	
	private long getPos(int row, int col){
		return 8 + (((long)row * (long)n_col) + col )* (long)sizeType;//( (row * n_col) + col ) * sizeType;
	}
	
	public void setElement(int row, int col, T value) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		if(row >= n_row)  n_row = row + 1;
		
		_matrixFile.seek(getPos(row, col));
		writeMethod.invoke(this, value);
	}

	public void setFullRow(int row, T[] fs) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		_matrixFile.seek(getPos(row, 0));
		for(int i = 0; i < fs.length ; i++){
			writeMethod.invoke(this, fs[i]);
		}
	}
	
	public void setFullMatrix(T [][] ma) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		n_row = ma.length;
		n_col = ma[0].length;
		writeSize();
		_matrixFile.seek(getPos(0, 0));
		for(int i = 0; i < ma.length ; i++){
			for(int j = 0; j < ma[i].length ; j++){
				writeMethod.invoke(this, ma[i][j]);
			}
			
		}
	}

	public void setFullRowFloat(int row, Float[] fs) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		_matrixFile.seek(getPos(row, 0));
		byte [] buffer = new byte[fs.length*(Float.SIZE/8)];
		ByteBuffer buf = ByteBuffer.wrap(buffer);
		for(int i = 0; i < fs.length ; i++){
			buf.putFloat(fs[i]);
		}
		_matrixFile.write(buffer);
	}
	
	
	public void setFullMatrixFloat(Float [][] ma) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		n_row = ma.length;
		n_col = ma[0].length;
		writeSize();
		_matrixFile.seek(getPos(0, 0));
		for(int i = 0; i < ma.length ; i++){
			byte [] buffer = new byte[n_col*(Float.SIZE/8)];
			ByteBuffer buf = ByteBuffer.wrap(buffer);
			for(int j = 0; j < ma[i].length ; j++){
				buf.putFloat(ma[i][j]);
			}
			_matrixFile.write(buffer);
		}
	}

	
	public Float [][] getFullMatrixFloat() throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		Float[][] tmp = new Float[n_row][n_col];
		_matrixFile.seek(getPos(0, 0));
		for(int i = 0; i < n_row ; i++){
			if (i%1000==0) System.out.print(""+i + ".");
			byte [] buffer = new byte[n_col*(Float.SIZE/8)];
			_matrixFile.read(buffer);
			ByteBuffer buf = ByteBuffer.wrap(buffer);
			for (int j=0; j<n_col;j++) {
				tmp[i][j] = buf.getFloat();
			}
		}
		System.out.println("" + n_row + " read!");
		return tmp;
	}

	public Float[] getFullRowFloat(int row) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		Float[] tmp = new Float[n_col];
 		_matrixFile.seek(getPos(row, 0));
		byte [] buffer = new byte[n_col*(Float.SIZE/8)];
		_matrixFile.read(buffer);
		ByteBuffer buf = ByteBuffer.wrap(buffer);
		for(int i = 0; i < n_col ; i++){
			tmp[i] = buf.getFloat();
		}
		return tmp;
	}
	
	public Float[] getNextFullRowFloat() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException {
		Float[] tmp = new Float[n_col];
		byte [] buffer = new byte[n_col*(Float.SIZE/8)];
		_matrixFile.read(buffer);
		ByteBuffer buf = ByteBuffer.wrap(buffer);
		for(int i = 0; i < n_col ; i++){
			tmp[i] = buf.getFloat();
		}
		currentrow++;
		return tmp;
	}
	
	public T [][] getFullMatrix() throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		T[][] tmp = (T[][]) Array.newInstance(tClass, n_row,n_col);
		_matrixFile.seek(getPos(0, 0));
		for(int i = 0; i < n_row ; i++){
			for(int j = 0; j < n_col ; j++){
				tmp[i][j] = (T) readMethod.invoke(this, null);
			}
		}
		return tmp;
		
	}

	
	
	public void setFullRow(int row, SortedMap<Integer, T> items) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		_matrixFile.seek(getPos(row, 0));
		for(int i : items.keySet()){
			writeMethod.invoke(this, items.get(i));
		}
	}
	
	public void setFullCol(int col, SortedMap<Integer, T> items) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		for(int i=0; i<n_row; i++){
			_matrixFile.seek(getPos(i, col));
			writeMethod.invoke(this, items.get(i));
		}

	}
	public void setFullCol(int col, T[] colelem) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		for(int i = 0; i < colelem.length ; i++){
			_matrixFile.seek(getPos(i, col));
			writeMethod.invoke(this, colelem[i]);
		}
	}
	public void setSparseRow(int row, Map<Integer, T> values) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		for(Integer i : values.keySet()){
			_matrixFile.seek(getPos(row, i));
			writeMethod.invoke(this, values.get(i));
		}
	}
	public void setSparseCol(int col, Map<Integer, T> values) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		for(Integer i : values.keySet()){
			_matrixFile.seek(getPos(i, col));
			writeMethod.invoke(this, values.get(i));
		}
	}
		
	public T getElement(int row, int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		_matrixFile.seek(getPos(row, col));
		return (T) readMethod.invoke(this, null);
	}
	public T[] getFullRow(int row) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		T[] tmp = (T[]) Array.newInstance(tClass, n_col);
 		_matrixFile.seek(getPos(row, 0));
		for(int i = 0; i < n_col ; i++){
			tmp[i] = (T)readMethod.invoke(this, null);
		}
		return tmp;
	}
	
	public void seekRowZero() throws IOException {
		_matrixFile.seek(getPos(0, 0));
		currentrow = 0;
	}

	public T[] getNextFullRow() throws IllegalArgumentException,
			IllegalAccessException, InvocationTargetException, IOException {
		T[] tmp = (T[]) Array.newInstance(tClass, n_col);
		currentrow++;
		for (int i = 0; i < n_col; i++) {
			tmp[i] = (T)readMethod.invoke(this, null);
		}
		return (T[]) tmp;
	}
	
	public TreeMap<Integer, T> getFullRowTreeMap(int row) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		TreeMap<Integer, T> res = new TreeMap<Integer, T>();
		_matrixFile.seek(getPos(row, 0));
		for(int i = 0; i < n_col ; i++){
			res.put(i,(T) readMethod.invoke(this, null));	
			
		}
		return res;
	}
	
	public TreeMap<Integer, T> getNextFullRowTreeMap() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		TreeMap<Integer, T> res = new TreeMap<Integer, T>();
		currentrow++;
		for(int i = 0; i < n_col ; i++){
			res.put(i,(T) readMethod.invoke(this, null));	
			
		}
		return res;
	}
	public TreeMap<Integer, T> getSparseRow(int row) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		TreeMap<Integer, T> res = new  TreeMap<Integer, T>();
		T zero = (T) new Float(0);
		_matrixFile.seek(getPos(row, 0));
		for(int i = 0; i < n_col ; i++){
			T val = (T) readMethod.invoke(this, null);
			
			if(!val.equals(zero)){
				res.put(i,val);	
			}
		}
		return res;
	}
	public TreeMap<Integer, T> getFullColTreeMap(int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		TreeMap<Integer, T> res = new TreeMap<Integer, T>();
		for(int i = 0; i < n_row ; i++){
			_matrixFile.seek(getPos(i, col));
			res.put(i,(T) readMethod.invoke(this, null));	
		}
		return res;
	}

	public T[] getFullCol(int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		T[] tmp = (T[]) Array.newInstance(tClass, n_row);
 		for(int i = 0; i < n_row ; i++){
			_matrixFile.seek(getPos(i, col));
			tmp[i] = (T)readMethod.invoke(this, null);
		}
		return tmp;
	}
	
	public TreeMap<Integer, T> getSparseCol(int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		TreeMap<Integer, T> res = new  TreeMap<Integer, T>();
		T zero = (T) new Float(0);
		for(int i = 0; i < n_row ; i++){
			_matrixFile.seek(getPos(i, col));
			T val = (T) readMethod.invoke(this, null);
			if(!val.equals(zero)){
				res.put(i,val);	
			}
		}
		return res;
	}
	
	public int getCols(){return n_col;}
	public int getRows(){return n_row;}

	public void setRows(int rows){n_row = rows;}
	
	public void execRowOperation(Operator op , int row, T val) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException, SecurityException, NoSuchMethodException{
//		Log._printlnFile("execRowOperation ("+row+"-"+n_row+"-"+n_col+"-"+val+")", "logs/mat.log");
		T[] rowelem = getFullRow(row);
//		System.out.print(".");
		Method operation = null;
		operation = this.getClass().getMethod(op.name().toLowerCase(), new Class[]{tClass, tClass});
		for(int i = 0 ; i < rowelem.length; i++) {
			rowelem[i] = (T)operation.invoke(this, rowelem[i],val);
		}
		setFullRow(row, rowelem);
	}
	
//	public void execColOperation(Operator op , int col, T val) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IOException, IllegalAccessException, InvocationTargetException{
//		T[] colelem = getFullCol(col);
//		Method operation = null;
//		operation = this.getClass().getMethod(op.name().toLowerCase(), new Class[]{tClass, tClass});
//		for(int i = 0 ; i < colelem.length; i++) {
//			colelem[i] = (T)operation.invoke(this, colelem[i],val);
//		}
//		setFullCol(col, colelem);
//	}
	
	public void convertToSparseByCol(String outFile) throws IllegalArgumentException, IOException, IllegalAccessException, InvocationTargetException{
		RandomAccessFile sparse = new RandomAccessFile(outFile,"rw");
		sparse.writeInt(n_row);
		sparse.writeInt(n_col);
		sparse.seek(12);
		int nonzero = 0;
		for(int i = 0; i < n_col ; i++){
			TreeMap<Integer, T> col = getSparseCol(i);
			sparse.writeInt(col.size());
			nonzero += col.size();
			for(int j : col.keySet()){
				Float e = (Float)col.get(j);
				sparse.writeInt(j);
				sparse.writeFloat(e);
			}
			System.out.print(".");
		}
		sparse.seek(8);
		sparse.writeInt(nonzero);
		sparse.close();
	}
	public void convertToSparseByRow(String outFile) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		RandomAccessFile sparse = new RandomAccessFile(outFile,"rw");
		sparse.writeInt(n_row);
		sparse.writeInt(n_col);
		sparse.seek(12);
		int nonzero = 0;
		for(int i = 0; i < n_row ; i++){
			TreeMap<Integer, T> row = getSparseRow(i);
			sparse.writeInt(row.size());
			nonzero += row.size();
			for(int j : row.keySet()){
				Float e = (Float)row.get(j);
				sparse.writeInt(j);
				sparse.writeFloat(e);
			}
		}
		sparse.seek(8);
		sparse.writeInt(nonzero);
		sparse.close();
	}
	
	
	
//	public T sum(T a, T b){Double res = ((Double)a+(Double)b); return (T)res;}
	public Double sum(Double a, Double b){Double res = a+b; return res;}
	public Float sum(Float a, Float b){Float res = a+b; return res;}
	public Integer sum(Integer a, Integer b){Integer res = a+b; return res;}
//	public T sum(Object a, Object b){Double res = ((Double)a+(Double)b); return (T)res;}
	
	public Double mult(Double a, Double b){return a*b;}
	public Float mult(Float a, Float b){return a*b;}
	public Integer mult(Integer a, Integer b){return a*b;}
//	public T mult(Object a, Object b){Double res = ((Double)a*(Double)b); return (T)res;}
	
//	public T div(T a, T b){Double res = ((Double)a/(Double)b); return (T)res;}
//	public T div(Object a, Object b){Double res = ((Double)a/(Double)b); return (T)res;}
	public Double div(Double a, Double b){return a/b;}
	public Float div(Float a, Float b){return a/b;}
	public Integer div(Integer a, Integer b){return a/b;}
	
	public void printAll() throws IOException, IllegalAccessException, InvocationTargetException{
		_matrixFile.seek(0);
		int rows= _matrixFile.readInt();
		int cols = _matrixFile.readInt();
		//int items = _matrixFile.readInt();
		System.out.println(rows+" "+cols);

		 
		for(int i= 0; i< rows; i++){
			System.out.println();
			for(int j = 0; j< cols; j++){
				_matrixFile.seek(getPos(i, j));
				System.out.print(readMethod.invoke(this, null)+"\t");
			}
		}
		System.out.println("\n");
	}

	public static void main(String[] args) {
		DenseBinaryMatrix<Float> mat = new DenseBinaryMatrix<Float>(Float.class);
		
		if(args.length!=1){
			System.out.println("Usage: "+DenseBinaryMatrix.class+" <matrix>");
			//System.exit(1);
		}
		
		try {

			System.out.println("openfile");
			mat.openFile(args[0], "r");

			mat.readSize();

			System.out.println(mat.getRows()+" "+mat.getCols());
			FileWriter output = new FileWriter(args[0] + ".dsm");
			for(int i = 0; i < mat.getRows(); i++){
				output.write(ArrayMath.arrayToString(mat.getFullRow(i)) + "\n");
				System.out.print('.');
			}
			output.close();
			System.out.println("Closing file");
			
			mat.closeFile();

		} catch (IOException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

}
